/*
 * C/C++ KLIB Project
 * 
 * Copyright (c) 2012 Ken Zhang (cpp.china@gmail.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA  02111-1307  USA
 *
 */


#include "klib.h"
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <stdio.h>

#define MAX_ARRAY_COUNT		1024

typedef struct _array_t {
	list_node_t *buf_list;
	size_t element_size;
} array_t;

array_t **_klibc_g_arraies = NULL;

int array_valid(int handle)
{
	if (_klibc_g_arraies && handle >= 0 && handle < MAX_ARRAY_COUNT) {
		if (_klibc_g_arraies[handle]) {
			return 1;
		}
	}
	return 0;
}

int array_create(size_t ele_size, size_t count)
{
	int i;
	if (_klibc_g_arraies == NULL) {
		_klibc_g_arraies = calloc(MAX_ARRAY_COUNT, sizeof(array_t));
	}
	if (ele_size && count) {
		for (i = 0; i < MAX_ARRAY_COUNT; i++) {
			if (_klibc_g_arraies[i] == NULL) {
				_klibc_g_arraies[i] = (array_t *)malloc(sizeof(array_t));
				if (_klibc_g_arraies[i]) {
					_klibc_g_arraies[i]->element_size = ele_size;
					_klibc_g_arraies[i]->buf_list = list_node_create(getbuf(ele_size * count));
					return i;
				}
			}
		}
	}
	return -1;
}

void array_destroy(int handle)
{
	list_node_t *node;
	if (array_valid(handle)) {
		if (_klibc_g_arraies[handle]) {
			node = _klibc_g_arraies[handle]->buf_list;
			list_foreach_data(node, delbuf);
			list_foreach(node, list_node_destroy);
		}
	}
}

size_t array_size(int handle)
{
	size_t n = 0;
	list_node_t *node;
	if (array_valid(handle)) {
		if (_klibc_g_arraies[handle]) {
			node = _klibc_g_arraies[handle]->buf_list;
			while (node) {
				n += ((buffer_t *)node->data)->size / _klibc_g_arraies[handle]->element_size;
				node = node->next;
			}
			return n;
		}
	}
	return 0;
}

size_t array_element_size(int handle)
{
	if (array_valid(handle)) {
		if (_klibc_g_arraies[handle]) {
			return _klibc_g_arraies[handle]->element_size;
		}
	}
	return 0;
}

void *array_element_at(int handle, int index)
{
	size_t n, siz;
	buffer_t *buf;
	list_node_t *node;

	if (array_valid(handle) && index >= 0) {
		if (_klibc_g_arraies[handle]) {
			node = _klibc_g_arraies[handle]->buf_list;
			siz = _klibc_g_arraies[handle]->element_size;
			for (;;) {
				buf = (buffer_t *)node->data;
				n = buf->size / siz;
				if (index < n) {
					break;
				}
				else {
					index -= n;
					node = node->next;
				}
			}

			if (node == NULL) {
				return NULL;
			}
			else {
				return &buf->data[index * siz];
			}
		}
	}
	return NULL;
}

void array_element_swap(int handle, int i, int j)
{
	if (array_valid(handle)) {
		void *a = array_element_at(handle, i);
		void *b = array_element_at(handle, j);
		if (a && b) {
			swap(a, b, array_element_size(handle));
		}
	}
}


void array_expand(int handle, size_t newsize)
{
	size_t n = 0, siz;
	list_node_t *node;
	if (array_valid(handle)) {
		if (_klibc_g_arraies[handle]) {
			node = _klibc_g_arraies[handle]->buf_list;
			siz = _klibc_g_arraies[handle]->element_size;
			while (node) {
				n += ((buffer_t *)node->data)->size / siz;
				node = node->next;
			}
			if (n < newsize) {
				list_push_back(_klibc_g_arraies[handle]->buf_list, list_node_create(getbuf((newsize - n) * siz)));
			}
		}
	}
}

void array_sort(int handle, int (*compare)(const void *, const void *))
{
	quicksort(
		(void *)handle, 
		(fn_get_element_t)array_element_at, 
		0, 
		array_size(handle) - 1, 
		compare, 
		(fn_swap_t)array_element_swap
	);
}

void array_test()
{
	int handle = array_create(sizeof(int), 8);
	int i;

	array_expand(handle, 16);
	array_expand(handle, 32);
	srand((unsigned int)time(NULL));
	for (i = 0; i < 32; i++) {
		*((int *)array_element_at(handle, i)) = rand() % 100;
	}
	array_sort(handle, compare_int_r);
	for (i = 0; i < 32; i++) {
		printf("%d\n", *((int *)array_element_at(handle, i)));
	}
}
